package cn.cdeden.common.oss.core;

import cn.cdeden.common.core.constant.Constants;
import cn.cdeden.common.core.exception.ServiceException;
import cn.cdeden.common.core.utils.DateUtils;
import cn.cdeden.common.core.utils.StringUtils;
import cn.cdeden.common.core.utils.file.FileUtils;
import cn.cdeden.common.oss.constant.OssConstant;
import cn.cdeden.common.oss.entity.UploadResult;
import cn.cdeden.common.oss.enumd.AccessPolicyType;
import cn.cdeden.common.oss.enumd.OssBucketOneEnum;
import cn.cdeden.common.oss.enumd.PolicyType;
import cn.cdeden.common.oss.exception.OssException;
import cn.cdeden.common.oss.properties.OssProperties;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.mock.web.MockMultipartFile;
import software.amazon.awssdk.auth.credentials.*;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.*;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipOutputStream;

/**
 * S3 存储协议 所有兼容S3协议的云厂商均支持
 * 阿里云 腾讯云 七牛云 minio
 *
 * @author AprilWind
 */
@Slf4j
public class OssClientBucket {

    /**
     * 服务商
     */
    private final String configKey;

    /**
     * 配置属性
     */
    private final OssProperties properties;

    /**
     * Amazon S3 异步客户端
     */
    private final S3AsyncClient client;

    /**
     * 用于管理 S3 数据传输的高级工具
     */
    private final S3TransferManager transferManager;

//    /**
//     * Amazon S3 同步客户端
//     */
//    private final S3Client syncClient;

    /**
     * AWS S3 预签名 URL 的生成器
     */
    private final S3Presigner presigner;

    /**
     * 构造方法
     *
     * @param configKey     配置键
     * @param ossProperties Oss配置属性
     */
    public OssClientBucket(String configKey, OssProperties ossProperties) {
        this.configKey = configKey;
        this.properties = ossProperties;

        try {
            // 创建 AWS 认证信息
            StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));

            // MinIO 使用 HTTPS 限制使用域名访问，站点填域名。需要启用路径样式访问
            boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);

            // 创建AWS基于 CRT 的 S3 客户端
            this.client = S3AsyncClient.crtBuilder()
                .credentialsProvider(credentialsProvider)
                .endpointOverride(URI.create(getEndpoint()))
                .region(of())
                .targetThroughputInGbps(20.0)
                .minimumPartSizeInBytes(10 * 1025 * 1024L)
                .checksumValidationEnabled(false)
                .forcePathStyle(isStyle)
                .httpConfiguration(S3CrtHttpConfiguration.builder()
                    .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
                    .build())
                .build();

            //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
            this.transferManager = S3TransferManager.builder().s3Client(this.client).build();

            // 创建 S3 配置对象
            S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
                .pathStyleAccessEnabled(isStyle).build();

            // 创建 预签名 URL 的生成器 实例，用于生成 S3 预签名 URL
            this.presigner = S3Presigner.builder()
                .region(of())
                .credentialsProvider(credentialsProvider)
                .endpointOverride(URI.create(getDomain()))
                .serviceConfiguration(config)
                .build();

            // 创建存储桶
//            createBucket();
        } catch (Exception e) {
            if (e instanceof OssException) {
                throw e;
            }
            throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
    }

    /**
     * 同步创建存储桶
     * 如果存储桶不存在，会进行创建；如果存储桶存在，不执行任何操作
     * 默认私有策略 打文件版本
     *
     * @param bucketName 桶名称
     * @throws OssException 当创建存储桶时发生异常时抛出
     */
    public void createBucket(String bucketName) {
        OssBucketOneEnum oneEnum = OssBucketOneEnum.findInfo(bucketName); //获取 是否有默认值
        if (oneEnum == null) {
            createBucket(bucketName, AccessPolicyType.PRIVATE, BucketVersioningStatus.ENABLED);
        }
        createBucket(bucketName,
            oneEnum.isStatus() ? AccessPolicyType.PUBLIC : AccessPolicyType.PRIVATE,  //策略
            BucketVersioningStatus.ENABLED); //开启文件版本
    }


    /**
     * 同步创建存储桶
     * 如果存储桶不存在，会进行创建；如果存储桶存在，不执行任何操作
     *
     * @param bucketName 桶名称
     * @param policyType 桶访问策略配置
     * @param status     文件是否有版本处理
     * @throws OssException 当创建存储桶时发生异常时抛出
     */
    public void createBucket(String bucketName, AccessPolicyType policyType, BucketVersioningStatus status) {
        try {
            // 尝试获取存储桶的信息
            HeadBucketResponse join = client.headBucket(
                    x -> x.bucket(bucketName)
                        .build())
                .join();
        } catch (Exception ex) {
            if (ex.getCause() instanceof NoSuchBucketException) {
                try {
                    // 存储桶不存在，尝试创建存储桶
                    client.createBucket(
                            x -> x.bucket(bucketName))
                        .join();

                    // 设置存储桶的访问策略（Bucket Policy）
                    client.putBucketPolicy(
                            x -> x.bucket(bucketName)
                                .policy(getPolicy(bucketName, policyType.getPolicyType())))
                        .join();

                    client.putBucketVersioning(
                            x -> x.bucket(bucketName)
                                .versioningConfiguration(j -> j.status(status)))
                        .join();

                } catch (S3Exception e) {
                    // 存储桶创建或策略设置失败
                    throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
                }
            } else {
                throw new OssException("判断Bucket是否存在失败，请核对配置信息:[" + ex.getMessage() + "]");
            }
        }
    }


    /**
     * 检查桶的策略和版本数据
     *
     * @param bucketName
     * @return void
     * @author Yu.
     * @Date 2024/12/6 10:53
     **/
    public void checkBucket(String bucketName) {
        try {
            // 尝试获取存储桶的信息
            HeadBucketResponse info = client.headBucket(
                    x -> x.bucket(bucketName)
                        .build())
                .join();
            OssBucketOneEnum anEnum = OssBucketOneEnum.findInfo(bucketName);
            if (anEnum != null) {
                //// 如果是系统的 桶这个直接 设置为 预设值
                client.putBucketPolicy(
                        x -> x.bucket(bucketName)
                            .policy(
                                getPolicy(bucketName, anEnum.isStatus() ?
                                    AccessPolicyType.PUBLIC.getPolicyType() : AccessPolicyType.PRIVATE.getPolicyType()
                                )
                            )
                    )
                    .join();
            }
        } catch (Exception ex) {
            // 存储桶不存在，尝试创建存储桶
            if (ex.getCause() instanceof NoSuchBucketException) {
                createBucket(bucketName);
            } else {
                throw new OssException("判断Bucket是否存在失败，请核对配置信息:[" + ex.getMessage() + "]");
            }
        }
    }

    /**
     * 判断桶 是否是公开的
     *
     * @param bucketName
     * @return boolean
     * @author Yu.
     * @Date 2024/12/6 10:50
     **/
    public boolean bucketPolicy(String bucketName) {
        try {
            // 尝试获取存储桶的信息
            GetBucketPolicyStatusResponse join = client.getBucketPolicyStatus(
                    x -> x.bucket(bucketName)
                        .build())
                .join();
            return join.policyStatus().isPublic();
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * 上传 InputStream 到 Amazon S3
     *
     * @param inputStream    要上传的输入流
     * @param coverImage     要文件图片流
     * @param bucketName     桶的名称
     * @param key            在 Amazon S3 中的对象键
     * @param keyImage       在 封面图片 中的对象键
     * @param contentType    文件内容类型
     * @param coverImageType 封面图片的类型
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult upload(InputStream inputStream,
                               InputStream coverImage,
                               String bucketName,
                               String key,
                               String keyImage,
                               String contentType,
                               String coverImageType
    ) {
        log.info("上传文件到 Amazon S3: bucketName={}, key={}", bucketName, key);
        // 如果输入流不是 ByteArrayInputStream，则将其读取为字节数组再创建 ByteArrayInputStream
        if (!(inputStream instanceof ByteArrayInputStream)) {
            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
        }
        //检查 桶是否存在
        checkBucket(bucketName); //去检查 并且检查策略
        String fileMd5 = "-1";
        FileInputStream fileInputStream = null;
        try {
            String bucketIamgeName = OssBucketOneEnum.PUBLIC_IMAGES.getCode();

            if (coverImage != null && FileUtils.isImage(coverImage)) {
//            if (coverImage != null ) {
                //设置缩略图
                try {
                    // 是否 有图片  有则 先删除文件
                    long available = coverImage.available();
                    // 创建异步请求体（length如果为空会报错）
                    BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
                        .contentLength(available)
                        .subscribeTimeout(Duration.ofSeconds(30))
                        .build();
                    //图片桶的名称
                    Upload upload = transferManager.upload(
                        x -> x.requestBody(body)
                            .putObjectRequest(
                                y -> y.bucket(bucketIamgeName)
                                    .key(keyImage)
                                    .contentType(coverImageType)
                                    .build())
                            .addTransferListener(LoggingTransferListener.create()) // 添加日志监听器
                            .build());
                    // 将输入流写入请求体
                    body.writeInputStream(coverImage);
                    // 等待文件上传操作完成
                    CompletedUpload uploadResult = upload.completionFuture().join();
                    String eTag = uploadResult.response().eTag();
                    String versionId = uploadResult.response().versionId(); //获取版本号
                } catch (Exception e) {
                    log.error("设置缩略图失败", e);
                }
            }


            // 获取文件大小
            long available = inputStream.available();

            // 创建异步请求体（length如果为空会报错）
            BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
                .contentLength(available)
                .subscribeTimeout(Duration.ofSeconds(30))
                .build();
            Upload upload = transferManager.upload(
                x -> x.requestBody(body)
                    .putObjectRequest(
                        y -> y.bucket(bucketName)
                            .key(key)
                            .contentType(contentType)
                            // 用于设置对象的访问控制列表（ACL）。不同云厂商对ACL的支持和实现方式有所不同，
                            // 因此根据具体的云服务提供商，你可能需要进行不同的配置（自行开启，阿里云有acl权限配置，腾讯云没有acl权限配置）
//                            .acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .addTransferListener(LoggingTransferListener.create()) // 添加日志监听器
                    .build());


            // 将输入流写入请求体
            body.writeInputStream(inputStream);

            // 等待文件上传操作完成
            CompletedUpload uploadResult = upload.completionFuture().join();
            String eTag = uploadResult.response().eTag();
            String versionId = uploadResult.response().versionId(); //获取版本号
            // 提取上传结果中的 ETag，并构建一个自定义的 UploadResult 对象
            return UploadResult.builder()
                .url(getUrl(bucketName) + StringUtils.SLASH + key)
                .filename(key)
                .bucketName(bucketName)
                .fileMd5(fileMd5)
                .fileSize(available)
                .versionId(versionId)
                .contentType(contentType)
                .coverName(keyImage)
                .coverUrl(keyImage != null ? getUrl(bucketIamgeName) + StringUtils.SLASH + keyImage : null)  // 获取缩略图地址
                .versionName(String.valueOf(System.currentTimeMillis())) // 获取
                .eTag(eTag).build();
        } catch (Exception e) {
            throw new OssException("上传文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 上传 文件的封面图
     *
     * @param coverImage  文件的封面图数据
     * @param key         文件的封面图名称
     * @param contentType 文件的封面图类型
     * @return cn.cdeden.common.oss.entity.UploadResult
     * @author Yu.
     * @Date 2025/2/7 9:45
     **/
    public UploadResult uploadCoverImage(InputStream coverImage, String key,  String contentType) {
        String bucketName = OssBucketOneEnum.PUBLIC_IMAGES.getCode();
        log.info("上传文件到 Amazon S3: bucketName={}, key={},", bucketName, key);
        // 如果输入流不是 ByteArrayInputStream，则将其读取为字节数组再创建 ByteArrayInputStream
        if (!(coverImage instanceof ByteArrayInputStream)) {
            coverImage = new ByteArrayInputStream(IoUtil.readBytes(coverImage));
        }
        if (!FileUtils.isImage(coverImage)) {
            throw new OssException("上传文件失败，请检查配置信息:[" + "文件不是图片" + "]");
        }

        //检查 桶是否存在
        checkBucket(bucketName); //去检查 并且检查策略
        String fileMd5 = "-1";
        FileInputStream fileInputStream = null;

        try {
            // 获取文件大小
            long available = coverImage.available();
            // 创建异步请求体（length如果为空会报错）
            BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
                .contentLength(available)
                .subscribeTimeout(Duration.ofSeconds(30))
                .build();
            Upload upload = transferManager.upload(
                x -> x.requestBody(body)
                    .putObjectRequest(
                        y -> y.bucket(bucketName)
                            .key(key)
                            .contentType(contentType)
                            // 用于设置对象的访问控制列表（ACL）。不同云厂商对ACL的支持和实现方式有所不同，
                            // 因此根据具体的云服务提供商，你可能需要进行不同的配置（自行开启，阿里云有acl权限配置，腾讯云没有acl权限配置）
//                            .acl(getAccessPolicy().getObjectCannedACL())
                            .build())
                    .addTransferListener(LoggingTransferListener.create()) // 添加日志监听器
                    .build());
            // 将输入流写入请求体
            body.writeInputStream(coverImage);

            // 等待文件上传操作完成
            CompletedUpload uploadResult = upload.completionFuture().join();
            String eTag = uploadResult.response().eTag();
            String versionId = uploadResult.response().versionId(); //获取版本号
            // 提取上传结果中的 ETag，并构建一个自定义的 UploadResult 对象
            return UploadResult.builder()
                .url(getUrl(bucketName) + StringUtils.SLASH + key)
                .filename(key)
                .bucketName(bucketName)
                .fileMd5(fileMd5)
                .fileSize((long) available)
                .versionId(versionId)
                .contentType(contentType)
                .versionName(String.valueOf(System.currentTimeMillis())) // 获取
                .eTag(eTag).build();
        } catch (Exception e) {
            throw new OssException("上传文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }


    public UploadResult uploadCoverImage(byte[] data, String coverSuffix, String contentType) {
        return uploadCoverImage(new ByteArrayInputStream(data), getPath(properties.getPrefix(), coverSuffix), contentType);
    }


    /**
     * 创建分片上传 请求
     *
     * @param bucketName 桶的名称
     * @param suffix     文件的后缀
     * @return java.lang.String
     * @author Yu.
     * @Date 2024/12/20 18:18
     **/
    public UploadResult createMultipartUpload(String bucketName, String suffix) {
        String key = getPath(properties.getPrefix(), suffix);
        CreateMultipartUploadResponse response = client.createMultipartUpload(b -> b.bucket(bucketName).key(key)).join();
        String uploadId = response.uploadId();
        log.error("创建分片上传编号,{}", uploadId);

        return UploadResult.builder()
            .filename(key) //文件在
            .bucketName(bucketName)
//            .url(getUrl(bucketName) + StringUtils.SLASH + key)
//            .fileMd5(fileMd5)
//            .fileSize((long) available)
//            .versionId(versionId)
//            .contentType(contentType)
//            .versionName(String.valueOf(System.currentTimeMillis())) // 获取
            .eTag(uploadId).build();
    }


    /**
     * @param bucketName 桶的名称
     * @param key        上传到的位置
     * @param bytes      要上传的文件
     * @param uploadId   上传编号
     * @param partNumber 分片编号
     * @param partSize   每片多少 5MB
     * @param length     全部文件文件有多大
     * @return software.amazon.awssdk.services.s3.model.CompletedPart
     * @author Yu.
     * @Description // TODO
     * @Date 2024/12/22 16:05
     **/
    public CompletedPart uploadPart(String bucketName, String key, byte[] bytes, String uploadId, int partNumber, long partSize, long length) {
        return uploadPart(bucketName, key, new ByteArrayInputStream(bytes), uploadId, partNumber, partSize, length);
    }

    /**
     * 上传分片信息
     *
     * @param bucketName  桶的名称
     * @param key         上传到的位置
     * @param inputStream 要上传的输入流
     * @param uploadId    上传编号
     * @param partNumber  分片编号
     * @param partSize    // 每片多少 5MB
     * @param length      全部文件文件有多大
     * @return software.amazon.awssdk.services.s3.model.CompletedPart
     * @author Yu.
     * @Date 2024/12/22 16:05
     **/
    public CompletedPart uploadPart(String bucketName, String key, InputStream inputStream, String uploadId, int partNumber, long partSize, long length) {
        int newPartNumber = partNumber - 1;
        long partLength = Math.min(partSize, length - (newPartNumber * partSize));     // 计算当前分片的长度
        ExecutorService executor = Executors.newFixedThreadPool(1); // 创建线程池
        try {
            CompletableFuture<UploadPartResponse> uploaded = client.uploadPart(
                UploadPartRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .partNumber(partNumber)
                    .contentLength(partLength)
                    .build()
                , AsyncRequestBody.fromInputStream(inputStream, partLength, executor)
            );
            UploadPartResponse join = uploaded.join(); // 等待上传完成
            String eTag = join.eTag();
            CompletedPart build = CompletedPart.builder().partNumber(partNumber).eTag(eTag).build();
            log.error("分片编号,{},分片大小,{},分片ETag,{}", partNumber, partLength, eTag);
            return build;
        } catch (Exception e) {
            throw new ServiceException("分片上传失败，分片编号," + partNumber + "错误信息:[" + e.getMessage() + "]");
        } finally {
            try {
                executor.shutdown(); // 关闭线程池
            } catch (Exception e) {
            }
        }

    }

    /**
     * 中止分段上传
     *
     * @param bucketName
     * @param key
     * @param uploadId
     * @return void
     * @author Yu.
     * @Date 2024/12/22 14:12
     **/
    public void abortMultipartUpload(String bucketName, String key, String uploadId) {
        try {
            CompletableFuture<AbortMultipartUploadResponse> upload = client.abortMultipartUpload(AbortMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(key)
                .uploadId(uploadId)
                .build());
            AbortMultipartUploadResponse join = upload.join();
            System.out.println("取消上传");
        } catch (Exception e) {
            throw new ServiceException("分片上传取消上传失败，请检查分片数据是否正确:[取消上传失败]");

        }
    }


    /**
     * 完整的分段上传
     *
     * @param bucketName
     * @param key
     * @param uploadId
     * @return void
     * @author Yu.
     * @Date 2024/12/22 14:13
     **/
    public UploadResult completeMultipartUpload(String bucketName, String key, String uploadId, int total) {
        //1.检查是否上传完成了
        List<CompletedPart> parts = sliceFileFinish(bucketName, key, uploadId);
        if (parts.size() != total) {
            throw new ServiceException("分片上传失败，请检查分片数据是否上传完毕:[文件的缺少" + (total - parts.size()) + "分片]");
        }
        CompletableFuture<CompleteMultipartUploadResponse> future = client.completeMultipartUpload(b -> b.bucket(bucketName).key(key).uploadId(uploadId).multipartUpload(b2 -> b2.parts(parts)));
        CompleteMultipartUploadResponse join = future.join();  // 等待合并完成
        String eTag = join.eTag();
        String versionId = join.versionId();
        return UploadResult.builder()
            .url(getUrl(bucketName) + StringUtils.SLASH + key)
            .filename(key)
            .bucketName(bucketName)
//            .fileMd5(fileMd5)
//            .fileSize((long) available)
            .versionId(versionId)
//            .contentType(contentType)
            .versionName(String.valueOf(System.currentTimeMillis())) // 获取
            .eTag(eTag).build();
    }


    /**
     * 创建分片文件是否上传完成
     *
     * @param bucketName 上传的桶位置
     * @param key        上传的key
     * @param uploadId   上传的编号
     * @param total      一共有多少分片数量
     * @return java.lang.String
     * @author Yu.
     * @Date 2024/12/22 14:20
     **/
    public String sliceFileFinish(String bucketName, String key, String uploadId, int total) {
        List<CompletedPart> completedParts = sliceFileFinish(bucketName, key, uploadId);
        int size = completedParts.size();
        if (size != total) {
            return "文件的缺少" + (total - size) + "分片";
        } else {
            return null;
        }
    }


    /**
     * 擦看分片文件上传了多少个
     *
     * @param bucketName
     * @param key
     * @param uploadId
     * @return java.util.List<software.amazon.awssdk.services.s3.model.CompletedPart>
     * @author Yu.
     * @Date 2024/12/22 14:26
     **/
    public List<CompletedPart> sliceFileFinish(String bucketName, String key, String uploadId) {
        List<CompletedPart> parts = new ArrayList<>();
        //1.查询上传了多少分片
        CompletableFuture<ListPartsResponse> listPartsResponse = client.listParts(ListPartsRequest.builder()
            .bucket(bucketName)
            .key(key)
            .uploadId(uploadId)
            .build());
        ListPartsResponse join = listPartsResponse.join();
        for (Part part : join.parts()) {
            CompletedPart build = CompletedPart.builder().partNumber(part.partNumber()).eTag(part.eTag()).build();
            parts.add(build);
//            System.out.println("零件 number: " + part.partNumber() + ", ETag: " + part.eTag());
        }
        return parts;
    }


    /**
     * 上传 byte[] 数据到 Amazon S3，使用指定的后缀构造对象键。
     *
     * @param data   要上传的 byte[] 数据
     * @param suffix 对象键的后缀
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType, String bucketName) {
        return upload(new ByteArrayInputStream(data), null, bucketName, getPath(properties.getPrefix(), suffix), null, contentType, null);
    }


    /**
     * 上传 byte[] 数据到 Amazon S3，使用指定的后缀构造对象键。
     *
     * @param data   要上传的 byte[] 数据
     * @param suffix 对象键的后缀
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadCoverImageSuffix(byte[] data, byte[] coverImage, String suffix, String contentType, String bucketName, String coverSuffix, String coverType) {
        return upload(new ByteArrayInputStream(data),
            coverImage != null ? new ByteArrayInputStream(coverImage) : null,
            bucketName,
            getPath(properties.getPrefix(), suffix),
            coverSuffix != null ? getPath(properties.getPrefix(), coverSuffix) : null,
            contentType,
            coverType != null ? coverType : null);
    }


    /**
     * 上传 byte[] 数据到 Amazon S3，使用指定的后缀构造对象键。  生成为历史版本
     *
     * @param data        要上传的 byte[] 数据
     * @param coverImage  要上传的 byte[] 封面数据
     * @param coverSuffix 对象键的后缀
     * @param pathKey     文件的路径
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadCoverImageSuffix(byte[] data, String contentType, String bucketName, String pathKey, byte[] coverImage, String coverSuffix, String coverType) {
        return upload(new ByteArrayInputStream(data),
            coverImage != null ? new ByteArrayInputStream(coverImage) : null,
            bucketName,
            pathKey,
            coverSuffix != null ? getPath(properties.getPrefix(), coverSuffix) : null,
            contentType,
            coverType);
    }


    /**
     * 上传 MockMultipartFile 数据到 Amazon S3，使用指定的后缀构造对象键。
     *
     * @param file       文件数据
     * @param suffix     后缀
     * @param bucketName 桶名称
     * @return cn.cdeden.common.oss.entity.UploadResult
     * @Date 2024/12/25 14:41
     **/
    public UploadResult uploadSuffix(MockMultipartFile file, String suffix, String bucketName) throws IOException {
        return upload(file.getInputStream(), null, bucketName, getPath(properties.getPrefix(), suffix), null, file.getContentType(), null);
    }


    /**
     * 上传 byte[] 数据到 Amazon S3，使用指定的后缀构造对象键。  生成为历史版本
     *
     * @param data    要上传的 byte[] 数据
     * @param suffix  对象键的后缀
     * @param pathKey 文件的路径
     * @return UploadResult 包含上传后的文件信息
     * @throws OssException 如果上传失败，抛出自定义异常
     */
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType, String bucketName, String pathKey) {
        return upload(new ByteArrayInputStream(data), null, bucketName, pathKey, null, contentType, null);
    }


    /**
     * 复制文件 数据到 Amazon S3，使用指定的构造对象键
     *
     * @param key           要复制的文件
     * @param suffix        对象键的后缀
     * @param bucketName    要复制的桶
     * @param versionId     要复制文件版本号
     * @param newBucketName 要复制到的桶
     * @return cn.cdeden.common.oss.entity.UploadResult
     * @author Yu.
     * @Date 2024/12/10 10:26
     **/
    public UploadResult copy(String key, String suffix, String bucketName, String versionId, String newBucketName) {
        //检查 桶是否存在
        checkBucket(bucketName); //去检查 并且检查策略
        checkBucket(newBucketName); //去检查 并且检查策略
        String fileMd5 = "-1";
        FileInputStream fileInputStream = null;
        try {
            Long available = null;
            if (versionId == null) {
                HeadObjectResponse join = client.headObject(x -> x.bucket(bucketName).key(key)).join();
                versionId = join.versionId();
                available = join.contentLength();
            } else {
                String finalVersionId1 = versionId;
                HeadObjectResponse join = client.headObject(x -> x.bucket(bucketName).versionId(finalVersionId1).key(key)).join();
                versionId = join.versionId();
                available = join.contentLength();
            }


            String destinationKey = getPath(properties.getPrefix(), suffix);
            String finalVersionId = versionId;
            Copy copy = transferManager.copy(x ->
                x.copyObjectRequest(y -> y
                        .sourceBucket(bucketName)
                        .sourceKey(key)
                        .sourceVersionId(finalVersionId)
                        .destinationBucket(newBucketName)
                        .destinationKey(destinationKey)
                    )
                    .addTransferListener(LoggingTransferListener.create()) // 添加日志监听器
                    .build());
            CompletedCopy completedCopy = copy.completionFuture().join();
            CopyObjectResponse response = completedCopy.response(); // 获取响应
            CopyObjectResult copyObjectResult = response.copyObjectResult();
            String eTag = copyObjectResult.eTag();
            String newVersionId = response.versionId(); //获取版本号
            // 提取上传结果中的 ETag，并构建一个自定义的 UploadResult 对象
            return UploadResult.builder()
                .url(getUrl(newBucketName) + StringUtils.SLASH + destinationKey)
                .filename(destinationKey)
                .bucketName(newBucketName)
                .fileMd5(fileMd5)
                .fileSize(available)
                .versionId(newVersionId)
                .versionName(String.valueOf(System.currentTimeMillis())) // 获取
                .eTag(eTag)
                .build();
        } catch (Exception e) {
            throw new OssException("复制文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 下载文件从 Amazon S3 到 输出流
     *
     * @param bucketName 桶的名称
     * @param key        文件在 Amazon S3 中的对象键
     * @param out        输出流
     * @return 输出流中写入的字节数（长度）
     * @throws OssException 如果下载失败，抛出自定义异常
     * @author Yu.
     * @Date 2024/12/23 15:56
     **/

    public long download(String bucketName, String key, OutputStream out) {
        return downloadAsynchronous(bucketName, key, null, out);
    }

    /**
     * 下载文件从 Amazon S3 到 输出流 异步的
     *
     * @param bucketName 桶的名称
     * @param objectKey  文件在 Amazon S3 中的对象键
     * @param versionId  文件版本
     * @param out        输出流
     * @return 输出流中写入的字节数（长度）
     * @throws OssException 如果下载失败，抛出自定义异常
     */
    public long downloadAsynchronous(String bucketName, String objectKey, String versionId, OutputStream out) {
        try {
            // 构建下载请求
            if (versionId == null) {
                HeadObjectResponse join = client.headObject(x -> x.bucket(bucketName).key(objectKey)).join();
                versionId = join.versionId();
            }
            String finalVersionId = versionId; //获取版本号
            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
                // 文件对象
                .getObjectRequest(y -> y.bucket(bucketName)
                    .key(objectKey)
                    .versionId(finalVersionId)
                    .build())
                .addTransferListener(LoggingTransferListener.create()) // 添加日志监听器
                // 使用订阅转换器
                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
                .build();
            // 使用 S3TransferManager 下载文件
            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
            // 输出到流中
            try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
                return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
            }
        } catch (Exception e) {
            throw new OssException("文件下载失败，错误信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 下载文件从 Amazon S3 到 输出流 同步的
     *
     * @param bucketName 桶的名称
     * @param objectKey  文件在 Amazon S3 中的对象键
     * @param versionId  文件版本
     * @param out        输出流
     * @return 输出流中写入的字节数（长度）
     * @throws OssException 如果下载失败，抛出自定义异常
     */
    public long downloadSynchronized(String bucketName, String objectKey, String versionId, OutputStream out) {
        try {
            // 构建下载请求
            if (versionId == null) {
                HeadObjectResponse join = client.headObject(x -> x.bucket(bucketName).key(objectKey)).join();
                versionId = join.versionId();
            }
            String finalVersionId = versionId; //获取版本号
            log.info("开始下载对象，版本号：{}", versionId); // 打印 versionId，帮助调试
            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
                // 文件对象
                .getObjectRequest(y -> y.bucket(bucketName)
                    .key(objectKey)
                    .versionId(finalVersionId)
                    .build())
                .addTransferListener(LoggingTransferListener.create()) // 添加日志监听器
                // 使用订阅转换器
                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
                .build();

            // 使用 S3TransferManager 下载文件并同步返回
            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);

            // 输出到流中  // 可自动关闭的流
            try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) {
                return responseStream.transferTo(out); // 阻塞调用线程 blocks the calling thread
            }
        } catch (Exception e) {
            throw new OssException("文件下载失败，错误信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 下载文件从 Amazon S3 到 本地路径
     *
     * @param bucketName 桶的名称
     * @param objectKey  文件在 Amazon S3 中的对象键
     * @param versionId  文件版本
     * @param filePath   本地路径
     * @return 输出流中写入的字节数（长度）
     * @throws OssException 如果下载失败，抛出自定义异常
     * @Date 2024/12/24 13:33
     **/
    public long downloadToLocal(String bucketName, String objectKey, String versionId, String filePath) {
        File directory = new File(filePath).getParentFile();  // 获取文件的父目录
        if (!directory.exists()) {
            boolean created = directory.mkdirs();  // 创建目录
            if (!created) {
                System.out.println("Failed to create directory: " + directory.getAbsolutePath());
                return -1;  // 或者根据需要返回一个错误代码
            }
        }
        try {
            // 构建下载请求
            if (versionId == null) {
                HeadObjectResponse join = client.headObject(x -> x.bucket(bucketName).key(objectKey)).join();
                versionId = join.versionId();
            }
            String finalVersionId = versionId; //获取版本号
//            DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
//                // 文件对象
//                .getObjectRequest(y -> y.bucket(bucketName)
//                    .key(objectKey)
//                    .versionId(finalVersionId)
//                    .build())
//                .addTransferListener(LoggingTransferListener.create())
//                // 使用订阅转换器
//                .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
//                .build();
//            Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
//            CompletedDownload<ResponseInputStream<GetObjectResponse>> join = responseFuture.completionFuture().join();
//            ResponseInputStream<GetObjectResponse> result = join.result();
//            return Files.copy(result, Paths.get(filePath));

            DownloadRequest<GetObjectResponse> downloadRequest = DownloadRequest.builder()
                // 文件对象
                .getObjectRequest(y -> y.bucket(bucketName)
                    .key(objectKey)
                    .versionId(finalVersionId)
                    .build())
                .addTransferListener(LoggingTransferListener.create())
                // 使用订阅转换器
                .responseTransformer(AsyncResponseTransformer.toFile(Paths.get(filePath)))
                .build();
            Download<GetObjectResponse> responseFuture = transferManager.download(downloadRequest);
            // 使用 S3TransferManager 下载文件
            CompletedDownload<GetObjectResponse> join = responseFuture.completionFuture().join();
            GetObjectResponse result = join.result();

            return result.contentLength();
        } catch (Exception e) {
            throw new OssException("文件下载失败，错误信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 放回 文件流
     *
     * @param bucketName 桶的名称
     * @param objectKey  文件在 Amazon S3 中的对象键
     * @return java.io.InputStream
     * @author Yu.
     * @Date 2024/12/23 16:04
     **/
    public InputStream downloadToInputStream(String bucketName, String objectKey) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            // 调用 downloadSynchronized 并将文件内容写入 OutputStream
            long downloadSize = downloadSynchronized(bucketName, objectKey, null, outputStream);
            // 可以在这里进行文件完整性检查，如检查文件大小
            if (downloadSize <= 0) {
                log.error("文件下载失败，文件大小为零");
                throw new OssException("文件下载失败");
            }
            log.error("文件下载成功，文件大小为{}", downloadSize);
            return new ByteArrayInputStream(outputStream.toByteArray());
        } finally {
            try {
                outputStream.close();  // 关闭 OutputStream 释放资源
                log.info("关闭 OutputStream 释放资源 ");
            } catch (IOException e) {
                log.error("关闭 ByteArrayOutputStream 时出错", e);
            }
        }
    }

    /**
     * 下载文件从 Amazon S3 到 本地文件
     *
     * @param bucketName
     * @param objectKey
     * @param filePath
     * @return
     */
    public File downloadToFile(String bucketName, String objectKey, String filePath) {
        long l = downloadToLocal(bucketName, objectKey, null, filePath);
        if (l == -1) {
            throw new OssException("文件下载失败，错误信息:[" + "文件下载失败，请检查文件是否正确" + "]");
        }
        return new File(filePath);
    }


    /**
     * 流 写入到zip 流中去
     *
     * @param bucketName 文件的桶位置
     * @param objectKey  文件的key
     * @param versionId  文件的版本号
     * @param out        zip 输出流
     * @return long
     * @author Yu.
     * @Description // TODO
     * @Date 2024/12/11 17:12
     **/
    public long downloadZip(String bucketName, String objectKey, String versionId, ZipOutputStream out) {
//        client.headObject(x -> x.bucket(bucketName).key(objectKey).versionId(versionId).build());
        DownloadRequest<ResponseInputStream<GetObjectResponse>> downloadRequest = DownloadRequest.builder()
            // 文件对象
            .getObjectRequest(y -> y.bucket(bucketName)
                .key(objectKey)
                .versionId(versionId)
                .build())
            .addTransferListener(LoggingTransferListener.create())
            // 使用订阅转换器
            .responseTransformer(AsyncResponseTransformer.toBlockingInputStream())
            .build();
        // 使用 S3TransferManager 下载文件
        Download<ResponseInputStream<GetObjectResponse>> responseFuture = transferManager.download(downloadRequest);
        // 输出到流中
        try (ResponseInputStream<GetObjectResponse> responseStream = responseFuture.completionFuture().join().result()) { // auto-closeable stream
            return responseStream.transferTo(out);
        } catch (Exception e) {
            throw new OssException("文件下载失败，错误信息:[" + e.getMessage() + "]");
        }


    }


    /******************************************************删除信息*********************************************************/


    /**
     * 删除云存储服务中指定路径下文件
     *
     * @param path 指定路径
     */
    public void delete(String bucketName, String path) {
        try {
            String url = removeBaseUrl(path, bucketName);
            ListObjectVersionsResponse join = client.listObjectVersions(
                x -> x.bucket(bucketName)
                    .prefix(url)
            ).join();

            List<ObjectVersion> versions = join.versions();
            for (ObjectVersion version : versions) {
                String versionId = version.versionId();
                client.deleteObject(
                    x -> x.bucket(bucketName)
                        .key(url)
                        .versionId(versionId)
                        .build());
            }
        } catch (Exception e) {
            throw new OssException("删除文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }

    /**
     * 删除云存储服务中指定路径下文件
     *
     * @param bucketName 桶的名称
     * @param path       指定路径
     * @param versionId  文件的版本
     */
    public void delete(String bucketName, String path, String versionId) {
        try {
            String url = removeBaseUrl(path, bucketName);
            client.deleteObject(
                x -> x.bucket(bucketName)
                    .key(url)
                    .versionId(versionId)
                    .build());
        } catch (Exception e) {
            throw new OssException("删除文件失败，请检查配置信息:[" + e.getMessage() + "]");
        }
    }


    /**
     * 移除路径中的基础URL部分，得到相对路径
     *
     * @param path 完整的路径，包括基础URL和相对路径
     * @return 去除基础URL后的相对路径
     */
    public String removeBaseUrl(String path, String bucketName) {
        return path.replace(getUrl(bucketName) + StringUtils.SLASH, "");
    }


    /**
     * 类工具方法
     **/

    /**
     * 获取私有URL链接
     *
     * @param bucketName //桶名称
     * @param objectKey  对象KEY
     * @param versionId  //文件夹版本
     * @param second     授权时间
     * @return java.lang.String
     * @author Yu.
     * @Date 2024/12/6 10:40
     **/
    public String getPrivateUrl(String bucketName, String objectKey, String versionId, Integer second) {
        // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
        if (versionId == null) { // 如果版本号不存在，则获取版本号
            HeadObjectResponse join = client.headObject(x -> x.bucket(bucketName).key(objectKey)).join();
            versionId = join.versionId();
        }
        String finalVersionId = versionId;
        URL url = presigner.presignGetObject(
                x -> x.signatureDuration(Duration.ofSeconds(second))
                    .getObjectRequest(
                        y -> y.bucket(bucketName)
                            .key(objectKey)
                            .versionId(finalVersionId)
                            .build())
                    .build())
            .url();
        return url.toString();
    }


    /**
     * 获取 S3 客户端的终端点 URL
     *
     * @return 终端点 URL
     */
    public String getEndpoint() {
        // 根据配置文件中的是否使用 HTTPS，设置协议头部
        String header = getIsHttps();
        // 拼接协议头部和终端点，得到完整的终端点 URL
        return header + properties.getEndpoint();
    }

    /**
     * 获取 S3 客户端的终端点 URL（自定义域名）
     *
     * @return 终端点 URL
     */
    public String getDomain() {
        // 从配置中获取域名、终端点、是否使用 HTTPS 等信息
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = getIsHttps();

        // 如果是云服务商，直接返回域名或终端点
        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
            return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
        }

        // 如果是 MinIO，处理域名并返回
        if (StringUtils.isNotEmpty(domain)) {
            return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain;
        }

        // 返回终端点
        return header + endpoint;
    }

    /**
     * 根据传入的 region 参数返回相应的 AWS 区域
     * 如果 region 参数非空，使用 Region.of 方法创建并返回对应的 AWS 区域对象
     * 如果 region 参数为空，返回一个默认的 AWS 区域（例如，us-east-1），作为广泛支持的区域
     *
     * @return 对应的 AWS 区域对象，或者默认的广泛支持的区域（us-east-1）
     */
    public Region of() {
        //AWS 区域字符串
        String region = properties.getRegion();
        // 如果 region 参数非空，使用 Region.of 方法创建对应的 AWS 区域对象，否则返回默认区域
        return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
    }


    public String getUrl() {
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = getIsHttps();
        // 云服务商直接返回
        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
            return header + (StringUtils.isNotEmpty(domain) ? domain : "." + endpoint);
        }
        // MinIO 单独处理
        if (StringUtils.isNotEmpty(domain)) {
            // 如果 domain 以 "https://" 或 "http://" 开头
            return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
                domain + StringUtils.SLASH : header + domain + StringUtils.SLASH;
        }
        return header + endpoint;
    }


    /**
     * 获取云存储服务的URL
     *
     * @return 文件路径
     */
    public String getUrl(String bucketName) {
        String domain = properties.getDomain();
        String endpoint = properties.getEndpoint();
        String header = getIsHttps();
        // 云服务商直接返回
        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
            return header + (StringUtils.isNotEmpty(domain) ? domain : bucketName + "." + endpoint);
        }
        // MinIO 单独处理
        if (StringUtils.isNotEmpty(domain)) {
            // 如果 domain 以 "https://" 或 "http://" 开头
            return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
                domain + StringUtils.SLASH + bucketName : header + domain + StringUtils.SLASH + bucketName;
        }
        return header + endpoint + StringUtils.SLASH + bucketName;
    }

    /**
     * 生成一个符合特定规则的、唯一的文件路径。通过使用日期、UUID、前缀和后缀等元素的组合，确保了文件路径的独一无二性
     *
     * @param prefix 前缀
     * @param suffix 后缀
     * @return 文件路径
     */
    public String getPath(String prefix, String suffix) {
        // 生成uuid
        String uuid = IdUtil.fastSimpleUUID();
        // 生成日期路径
        String datePath = DateUtils.datePath();
        // 拼接路径
        String path = StringUtils.isNotEmpty(prefix) ?
            prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
        return path + "." + suffix;
    }

//    /**
//     * 移除路径中的基础URL部分，得到相对路径
//     *
//     * @param path 完整的路径，包括基础URL和相对路径
//     * @return 去除基础URL后的相对路径
//     */
//    public String removeBaseUrl(String path) {
//        return path.replace(getUrl() + StringUtils.SLASH, "");
//    }

    /**
     * 服务商
     */
    public String getConfigKey() {
        return configKey;
    }

    /**
     * 获取是否使用 HTTPS 的配置，并返回相应的协议头部。
     *
     * @return 协议头部，根据是否使用 HTTPS 返回 "https://" 或 "http://"
     */
    public String getIsHttps() {
        return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
    }

    /**
     * 检查配置是否相同
     */
    public boolean checkPropertiesSame(OssProperties properties) {
        return this.properties.equals(properties);
    }

    /**
     * 获取当前桶权限类型
     *
     * @return 当前桶权限类型code
     */
    public AccessPolicyType getAccessPolicy() {
        return AccessPolicyType.getByType(properties.getAccessPolicy());
    }

    /**
     * 生成 AWS S3 存储桶访问策略
     *
     * @param bucketName 存储桶
     * @param policyType 桶策略类型
     * @return 符合 AWS S3 存储桶访问策略格式的字符串
     */
    private static String getPolicy(String bucketName, PolicyType policyType) {
        String policy = switch (policyType) {
            case WRITE -> """
                {
                  "Version": "2012-10-17",
                  "Statement": []
                }
                """;
            case READ_WRITE -> """
                {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": [
                        "s3:GetBucketLocation",
                        "s3:ListBucket",
                        "s3:ListBucketMultipartUploads"
                      ],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": [
                        "s3:AbortMultipartUpload",
                        "s3:DeleteObject",
                        "s3:GetObject",
                        "s3:ListMultipartUploadParts",
                        "s3:PutObject"
                      ],
                      "Resource": "arn:aws:s3:::bucketName/*"
                    }
                  ]
                }
                """;
            case READ -> """
                {
                  "Version": "2012-10-17",
                  "Statement": [
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": ["s3:GetBucketLocation"],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Deny",
                      "Principal": "*",
                      "Action": ["s3:ListBucket"],
                      "Resource": "arn:aws:s3:::bucketName"
                    },
                    {
                      "Effect": "Allow",
                      "Principal": "*",
                      "Action": "s3:GetObject",
                      "Resource": "arn:aws:s3:::bucketName/*"
                    }
                  ]
                }
                """;
        };
        return policy.replaceAll("bucketName", bucketName);
    }

}
